package com.sunnada.nginx.service;

import com.github.odiszapc.nginxparser.NgxBlock;
import com.github.odiszapc.nginxparser.NgxConfig;
import com.github.odiszapc.nginxparser.NgxEntry;
import com.sunnada.nginx.bean.nginx.http.upstream.Upstream;
import com.sunnada.nginx.common.NginxUtils;
import com.sunnada.nginx.exception.CustonException;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

@Service("upstreamService")
public class UpstreamServiceImpl implements UpstreamService {

    @Autowired
    NginxUtils nginxUtils;

    @Autowired
    StatusService statusService;

    /**
     * 获取配置中的upstream列表
     */
    public List<Upstream> list() throws CustonException {
        List<Upstream> result = new ArrayList<>();
        NgxConfig conf = nginxUtils.read();
        List<NgxEntry> findAll = conf.findAll(NgxConfig.BLOCK, "http", "upstream");
        for (NgxEntry ngxEntry : findAll) {
            NgxBlock block = (NgxBlock) ngxEntry;
            Upstream upstream = new Upstream(block);
            result.add(upstream);
        }
        return result;
    }

    /**
     * 根据姓名查询upstream
     */
    public Upstream one(String name) throws CustonException {
        if (StringUtils.isBlank(name)) throw new CustonException("查询的upstream块不存在");
        NgxConfig conf = nginxUtils.read();
        List<NgxEntry> findAll = conf.findAll(NgxConfig.BLOCK, "http", "upstream");
        for (NgxEntry ngxEntry : findAll) {
            NgxBlock block = (NgxBlock) ngxEntry;
            if (!name.equals(block.getValue())) continue;
            return new Upstream(block);
        }
        throw new CustonException("查询的upstream块不存在");
    }

    /**
     * 处理upstrea的post请求
     */
    public boolean post(Upstream upstream) throws CustonException {
        // 校验参数的有效值
        upstream.vali();
        // 读取配置
        NgxConfig conf = nginxUtils.read();
        // 备份配置
        String backConf = NginxUtils.toString(conf);
        // 判断upstream是否存在
        // 存在则抛出异常，不存在则执行添加操作
        if (upstreamIsExistInConf(conf, upstream)) throw new CustonException("该upstream已存在，无法新增");
        // 添加upstream
        addUpstream(conf, upstream);
        // 测试配置文件是否合法
        checkConf(conf, backConf);
        return true;
    }

    /**
     * 向conf中添加upstream
     */
    private void addUpstream(NgxConfig conf, Upstream upstream) {
        NgxBlock httpBlock = (NgxBlock) conf.find(NgxConfig.BLOCK, "http");
        httpBlock.addEntry(upstream.toNgxBlock());
    }

    /**
     * 处理upstream的put请求
     */
    public boolean put(Upstream upstream) throws CustonException {
        // 校验参数的有效值
        upstream.vali();
        // 读取配置
        NgxConfig conf = nginxUtils.read();
        // 备份配置
        String backConf = NginxUtils.toString(conf);
        // 判断upstream是否存在
        // 不存在则抛出异常，存在则执行添加操作
        if (!upstreamIsExistInConf(conf, upstream)) throw new CustonException("该upstream不存在，无法修改");
        // 修改配置中的同名upstream
        updateUpstream(conf, upstream);
        // 测试配置文件是否合法
        checkConf(conf, backConf);
        return true;
    }

    /**
     * 更新conf中的同名upstream
     * 删除原有upstream再进行添加
     */
    private void updateUpstream(NgxConfig conf, Upstream upstream) {
        NgxBlock httpBlock = (NgxBlock) conf.find(NgxConfig.BLOCK, "http");
        deleteUpstream(conf, upstream);
        httpBlock.addEntry(upstream.toNgxBlock());
    }

    /**
     * 处理upstream的delete请求
     */
    public boolean delete(String name) throws CustonException {
        // 读取配置
        NgxConfig conf = nginxUtils.read();
        // 备份配置
        String backConf = NginxUtils.toString(conf);
        // 判断upstream是否存在
        // 不存在则抛出异常，存在则执行添加操作
        if (!upstreamIsExistInConf(conf, name)) throw new CustonException("该upstream不存在，无法删除");
        // 修改配置中的同名upstream
        deleteUpstream(conf, name);
        // 测试配置文件是否合法
        checkConf(conf, backConf);
        return true;
    }

    /**
     * @see UpstreamServiceImpl#deleteUpstream(NgxConfig, String)
     */
    private void deleteUpstream(NgxConfig conf, Upstream upstream) {
        deleteUpstream(conf, upstream.getName());
    }

    /**
     * 删除conf中的名字为name的upstream
     */
    private void deleteUpstream(NgxConfig conf, String name) {
        NgxBlock httpBlock = (NgxBlock) conf.find(NgxConfig.BLOCK, "http");
        List<NgxEntry> upstreamBlock = conf.findAll(NgxConfig.BLOCK, "http", "upstream");
        for (NgxEntry entry : upstreamBlock) {
            NgxBlock block = (NgxBlock) entry;
            if (name.equals(block.getValue())) {
                httpBlock.remove(block);
                return;
            }
        }
    }

    /**
     * 使用新的配置重启ngixn，如果重启失败，则回滚配置
     *
     * @param conf 新配置
     * @param back 配置备份
     */
    private void checkConf(NgxConfig conf, String back) throws CustonException {
        try {
            nginxUtils.save(conf);
            //重启Nginx
            statusService.checkConf();
        } catch (Exception e) {
            //恢复到上一次配置
            nginxUtils.save(back);
            throw new CustonException("已恢复到上一次配置");
        }
    }

    /**
     * 判断conf中是否存在对应的upstream
     */
    private boolean upstreamIsExistInConf(NgxConfig conf, Upstream upstream) {
        return upstreamIsExistInConf(conf, upstream.getName());
    }

    /**
     * 判断配置中是否存在对应的upstream
     * 以upstream的name作为判断依据
     * @return true则表示存在，false则表示不存在
     */
    private boolean upstreamIsExistInConf(NgxConfig conf, String upstreamName) {
        List<NgxEntry> findAll = conf.findAll(NgxConfig.BLOCK, "http", "upstream");
        for (NgxEntry ngxEntry : findAll) {
            NgxBlock block = (NgxBlock) ngxEntry;
            if (upstreamName.equals(block.getValue())) return true;
        }
        return false;
    }
}
